package ca.swank.hardquor;

import java.util.LinkedList;
import java.util.TreeSet;
import java.util.Comparator;

/**
* A basic implementation of hardquorBoard.
*
* @author Jesse Wilson
* @version 1.0 10/18/2001
*/
public class hardquorSimpleBoard implements hardquorBoard, Cloneable
{

  // INTERNAL CLASS REQUIRED LOGIC STUFF
  private static int[] STATUS;
  static
  { STATUS = new int[7];
    STATUS[DIR_ABOVE] = 1;
    STATUS[DIR_RIGHT] = 2;
    STATUS[DIR_BELOW] = 4;
    STATUS[DIR_LEFT]  = 8;
    STATUS[DIR_CRISSCROSS] = 16;
    STATUS[PLAYER_ONE] = 32;
    STATUS[PLAYER_TWO] = 64;
  }

  // BOARD STUFF
  private int[][] board;

  // PLAYER STUF
  private int[][] playerLocations;

  // WALL STUFF
  private int[] playerWalls;

  // PLAYER FINISH STUFF
  private int[][][] playerFinishes;

  // GAME LOGIC STUFF
  private int turn;
  private int winner;

  // INTERNAL TOOLS
  private static Comparator coordCompare = hardquorBoardTools.getCoordinateComparator();

  public hardquorSimpleBoard()
  { this(9,11,10);
  }

  public hardquorSimpleBoard(hardquorSimpleBoard other)
  {
    board = new int[other.board.length][other.board[0].length];
    for(int y = board[0].length - 1; y >= 0; y--)
    { for(int x = 0; x < board.length; x++)
      { board[x][y] = other.board[x][y];
      }
    }

    playerLocations = new int[other.playerLocations.length][other.playerLocations[0].length];
    for(int p = 0; p < playerLocations.length; p++)
    { for(int q = 0; q < playerLocations[0].length; q++)
      { playerLocations[p][q] = other.playerLocations[p][q];
      }
    }

    playerWalls = new int[other.playerWalls.length];
    for(int p = 0; p < playerWalls.length; p++)
    { playerWalls[p] = other.playerWalls[p];
    }

    playerFinishes = new int[other.playerFinishes.length][other.playerFinishes[0].length][other.playerFinishes[0][0].length];
    for(int p = 0; p < playerFinishes.length; p++)
    { for(int q = 0; q < playerFinishes[0].length; q++)
      { for(int r = 0; r < playerFinishes[0][0].length; r++)
        { playerFinishes[p][q][r] = other.playerFinishes[p][q][r];
      }
    }
    }

    turn = other.turn;
    winner = other.winner;
  }

  public hardquorSimpleBoard(int width, int height, int playerStartWalls)
  {
    // BOARD STUFF
    // initialize empty board array
    // set up board walls on perimeter, and at top and bottom
  board = new int[width][height];
    for(int y = board[0].length - 1; y >= 0; y--)
    { for(int x = 0; x < board.length; x++)
      { board[x][y] = 0;
        if(x == 0 || y == board[0].length - 1 || y == 0)
          setSquareStatus(x, y, DIR_LEFT);
        if(x == board.length - 1 || y == board[0].length - 1 || y == 0)
          setSquareStatus(x, y, DIR_RIGHT);
        if(y == 0)
          setSquareStatus(x, y, DIR_BELOW);
        if(y == board[0].length - 1)
          setSquareStatus(x, y, DIR_ABOVE);
      }
    }

    // PLAYER STUFF
    // set player locations
    playerLocations = new int[2][];
    playerLocations[PLAYER_ONE] = new int[] { 4, 0 };
    playerLocations[PLAYER_TWO] = new int[] { 4, 10 };
    // add players to board
    for(int p = PLAYER_ONE; p <= PLAYER_TWO; p++)
    { setSquareStatus(playerLocations[p][0], playerLocations[p][1], p);
    }

    // WALL STUFF
    // initialize player wall arrays
    playerWalls = new int[2];
    for(int p = PLAYER_ONE; p <= PLAYER_TWO; p++)
    { playerWalls[p] = playerStartWalls;
    }

    // PLAYER FINISH STUFF
    // set player finish locations
    playerFinishes = new int[2][board.length][];
    for(int x = 0; x < board.length; x++)
    { playerFinishes[PLAYER_ONE][x] = new int[] { x, 10 };
      playerFinishes[PLAYER_TWO][x] = new int[] { x, 0  };
    }

    // GAME LOGIC STUFF
    // initialize turn
    turn = PLAYER_ONE;
    // initialize winner
    winner = PLAYER_NONE;

  }

  private boolean getSquareStatus(int x, int y, int dirOrPlayer)
  { return (board[x][y] % (STATUS[dirOrPlayer] * 2) >= STATUS[dirOrPlayer]);
  }

  private boolean setSquareStatus(int x, int y, int dirOrPlayer)
  { if(getSquareStatus(x,y,dirOrPlayer)) return true;
    else board[x][y] = board[x][y] + STATUS[dirOrPlayer];
    return false;
  }

  private boolean clearSquareStatus(int x, int y, int dirOrPlayer)
  { if(!getSquareStatus(x,y,dirOrPlayer)) return false;
    board[x][y] = board[x][y] - STATUS[dirOrPlayer];
    return true;
  }

  private boolean isValidCoordinate(int[] coordinate)
  { if(coordinate.length != 2) return false;
    if(coordinate[0] < 0 || coordinate[0] >= board.length) return false;
    if(coordinate[1] < 0 || coordinate[1] >= board[0].length) return false;
    return true;
  }

  public int getWinner()
  { return winner;
  }

  public int getTurn()
  { return turn;
  }

  public TreeSet getLegalMoves(int[] origin)
  { TreeSet legalMoves = new TreeSet(coordCompare);
    TreeSet playerLocationsCompleted = new TreeSet(coordCompare);
    LinkedList playerLocationsQueue = new LinkedList();

    playerLocationsQueue.addLast(origin);

    int[] current;
    int[] leaf;
    while(!playerLocationsQueue.isEmpty())
    { current = (int[])playerLocationsQueue.removeFirst();
      playerLocationsCompleted.add(current);
      for(int d = DIR_ABOVE; d <= DIR_LEFT; d++)
      { if(isAdjacent(current, d))
        { leaf = hardquorBoardTools.getSquareInDirection(current, d);
          if(getSquareOccupant(leaf) == PLAYER_NONE)
          { legalMoves.add(leaf);
          } else if(!playerLocationsCompleted.contains(leaf))
          { playerLocationsQueue.addLast(leaf);
          }
      }
      }
    }
    return legalMoves;
  }


  public int getPlayerWalls(int playerNumber)
  { return playerWalls[playerNumber];
  }

  public int getSquareOccupant(int[] square)
  { for(int p = PLAYER_ONE; p <= PLAYER_TWO; p++)
    { if(getSquareStatus(square[0], square[1], p)) return p;
    }
    return PLAYER_NONE;
  }

  public int[] getPlayerLocation(int player)
  { return playerLocations[player];
  }

  public int[][] getPlayerFinish(int player)
  { return playerFinishes[player];
  }

  public int isLegalMove(Move move)
  { if(move instanceof WallMove) return isLegalMove((WallMove)move);
    else return isLegalMove((WalkMove)move);
  }

  public int isLegalMove(WalkMove walkMove)
  { return isLegalMove(walkMove.source(), walkMove.target());
  }

  public int isLegalMove(int[] source, int[] target)
  {
    if(!isValidCoordinate(source))
      return hardquorUserGame.ERROR_INVALID_MOVE;

    if(!isValidCoordinate(target))
      return hardquorUserGame.ERROR_INVALID_MOVE;

    if(winner != PLAYER_NONE)
      return hardquorUserGame.ERROR_GAME_COMPLETE;

    if(getSquareOccupant(source) != turn)
      return hardquorUserGame.ERROR_WRONG_PLAYER;

    if(getSquareOccupant(target) != PLAYER_NONE)
      return hardquorUserGame.ERROR_OCCUPIED_TARGET;

    TreeSet legalMoves = getLegalMoves(source);
    int[] current;
    boolean found = false;
    while(!legalMoves.isEmpty())
    { current = (int[])legalMoves.first();
      legalMoves.remove(current);
      if(coordCompare.compare(current,target) == 0) found = true;
    }

    if(!found)
      return hardquorUserGame.ERROR_NOT_ADJACENT;

    return hardquorUserGame.ERROR_NONE;

  }

  public int isLegalMove(WallMove wallMove)
  { return isLegalMove(wallMove.a1(), wallMove.a2(), wallMove.b1(), wallMove.b2());
  }

  public int isLegalMove(int[] a1, int[] a2, int[] b1, int[] b2)
  {
    if(!isValidCoordinate(a1))
      return hardquorUserGame.ERROR_INVALID_MOVE;

    if(!isValidCoordinate(a2))
      return hardquorUserGame.ERROR_INVALID_MOVE;

    if(!isValidCoordinate(b1))
      return hardquorUserGame.ERROR_INVALID_MOVE;

    if(!isValidCoordinate(b2))
      return hardquorUserGame.ERROR_INVALID_MOVE;

    if(winner != PLAYER_NONE)
      return hardquorUserGame.ERROR_GAME_COMPLETE;

    if(playerWalls[turn] <= 0)
      return hardquorUserGame.ERROR_INSUFFICIENT_WALLS;

    boolean horizontal = checkHorizontal(a1, a2, b1, b2);

    int[] bottomLeft = getBottomLeft(a1, a2, b1, b2);
    int[] bottomRight = hardquorBoardTools.getSquareInDirection(bottomLeft, DIR_RIGHT);
    int[] topLeft = hardquorBoardTools.getSquareInDirection(bottomLeft, DIR_ABOVE);
    int[] topRight = hardquorBoardTools.getSquareInDirection(topLeft, DIR_RIGHT);

    if(horizontal)
    { if(!isAdjacent(bottomLeft, DIR_ABOVE))
        return hardquorUserGame.ERROR_NOT_ADJACENT;
      if(!isAdjacent(bottomRight, DIR_ABOVE))
        return hardquorUserGame.ERROR_NOT_ADJACENT;
      if(!isAdjacent(topLeft, DIR_BELOW))
        return hardquorUserGame.ERROR_NOT_ADJACENT;
      if(!isAdjacent(topRight, DIR_BELOW))
        return hardquorUserGame.ERROR_NOT_ADJACENT;
    } else
    { if(!isAdjacent(bottomLeft, DIR_RIGHT))
        return hardquorUserGame.ERROR_NOT_ADJACENT;
      if(!isAdjacent(topLeft, DIR_RIGHT))
        return hardquorUserGame.ERROR_NOT_ADJACENT;
      if(!isAdjacent(bottomRight, DIR_LEFT))
        return hardquorUserGame.ERROR_NOT_ADJACENT;
      if(!isAdjacent(topRight, DIR_LEFT))
        return hardquorUserGame.ERROR_NOT_ADJACENT;
    }
    if(!isAdjacent(bottomLeft, DIR_CRISSCROSS))
      return hardquorUserGame.ERROR_WALL_CRISSCROSS;

    int[][] distanceBoard;
    for(int p = PLAYER_ONE; p <= PLAYER_TWO; p++)
    { makeMove(a1, a2, b1, b2);
      distanceBoard = getDistanceHomeBoard(p);
      unMove(a1, a2, b1, b2);
      if(distanceBoard[playerLocations[p][0]][playerLocations[p][1]] == Integer.MAX_VALUE)
      { return hardquorUserGame.ERROR_PLAYER_BLOCKED;
      }
    }

    return hardquorUserGame.ERROR_NONE;
  }

  public static int[] getBottomLeft(int[] a1, int[] a2, int[] b1, int[] b2)
  { int[] bottomLeft = a1;
    if(a2[0] <= bottomLeft[0] && a2[1] <= bottomLeft[1]) bottomLeft = a2;
    if(b1[0] <= bottomLeft[0] && b1[1] <= bottomLeft[1]) bottomLeft = b1;
    if(b2[0] <= bottomLeft[0] && b2[1] <= bottomLeft[1]) bottomLeft = b2;
    return bottomLeft;
  }

  protected boolean checkHorizontal(int[] a1, int[] a2, int[] b1, int[] b2)
  { return (a1[1] == a2[1]);
  }

  public void makeMove(WallMove wallMove)
  { makeMove(wallMove.a1(), wallMove.a2(), wallMove.b1(), wallMove.b2());
  }

  public void makeMove(int[] a1, int[] a2, int[] b1, int[] b2)
  {
    // modify board
    boolean horizontal = checkHorizontal(a1, a2, b1, b2);

    int[] bottomLeft = getBottomLeft(a1, a2, b1, b2);
    int[] bottomRight = hardquorBoardTools.getSquareInDirection(bottomLeft, DIR_RIGHT);
    int[] topLeft = hardquorBoardTools.getSquareInDirection(bottomLeft, DIR_ABOVE);
    int[] topRight = hardquorBoardTools.getSquareInDirection(topLeft, DIR_RIGHT);

    if(horizontal)
    { setSquareStatus(bottomLeft[0],  bottomLeft[1],  DIR_ABOVE);
      setSquareStatus(bottomRight[0], bottomRight[1], DIR_ABOVE);
      setSquareStatus(topLeft[0],     topLeft[1],     DIR_BELOW);
      setSquareStatus(topRight[0],    topRight[1],    DIR_BELOW);
    } else
    { setSquareStatus(bottomLeft[0],  bottomLeft[1],  DIR_RIGHT);
      setSquareStatus(topLeft[0],     topLeft[1],     DIR_RIGHT);
      setSquareStatus(bottomRight[0], bottomRight[1], DIR_LEFT);
      setSquareStatus(topRight[0],    topRight[1],    DIR_LEFT);
    }

    setSquareStatus(bottomLeft[0], bottomLeft[1], DIR_CRISSCROSS);

    // remove wall from player
    playerWalls[turn] = playerWalls[turn] - 1;

    // change turn
    changeTurn();
  }

  protected void changeTurn()
  { if(turn == PLAYER_ONE)      turn = PLAYER_TWO;
    else if(turn == PLAYER_TWO) turn = PLAYER_ONE;
  }

  public void makeMove(Move move)
  { if(move instanceof WallMove) makeMove((WallMove)move);
    else makeMove((WalkMove)move);
  }

  public void makeMove(WalkMove walkMove)
  { makeMove(walkMove.source(), walkMove.target());
  }

  public void makeMove(int[] source, int[] target)
  { // modify board
    clearSquareStatus(source[0], source[1], turn);
    setSquareStatus(target[0], target[1], turn);

    // change player location
    playerLocations[turn] = target;

    // check if we have a winner
    for(int i = 0; i < playerFinishes[turn].length; i++)
    { if(hardquorBoardTools.coordsEqual(target, playerFinishes[turn][i]))
      { winner = turn;
        break;
      }
    }

    // change turn
    changeTurn();

  }

  protected void unMove(int[] a1, int[] a2, int[] b1, int[] b2)
  { changeTurn();
    playerWalls[turn] = playerWalls[turn] + 1;

  // modify board
    boolean horizontal = checkHorizontal(a1, a2, b1, b2);

    int[] bottomLeft = getBottomLeft(a1, a2, b1, b2);
    int[] bottomRight = hardquorBoardTools.getSquareInDirection(bottomLeft, DIR_RIGHT);
    int[] topLeft = hardquorBoardTools.getSquareInDirection(bottomLeft, DIR_ABOVE);
    int[] topRight = hardquorBoardTools.getSquareInDirection(topLeft, DIR_RIGHT);

    if(horizontal)
    { clearSquareStatus(bottomLeft[0],  bottomLeft[1],  DIR_ABOVE);
      clearSquareStatus(bottomRight[0], bottomRight[1], DIR_ABOVE);
      clearSquareStatus(topLeft[0],     topLeft[1],     DIR_BELOW);
      clearSquareStatus(topRight[0],    topRight[1],    DIR_BELOW);
    } else
    { clearSquareStatus(bottomLeft[0],  bottomLeft[1],  DIR_RIGHT);
      clearSquareStatus(topLeft[0],     topLeft[1],     DIR_RIGHT);
      clearSquareStatus(bottomRight[0], bottomRight[1], DIR_LEFT);
      clearSquareStatus(topRight[0],    topRight[1],    DIR_LEFT);
    }

    clearSquareStatus(bottomLeft[0], bottomLeft[1], DIR_CRISSCROSS);
  }

  protected void unMove(int[] source, int[] target)
  { changeTurn();
    winner = PLAYER_NONE;
    playerLocations[turn] = source;
    clearSquareStatus(target[0], target[1], turn);
    setSquareStatus(source[0], source[1], turn);
  }


  public boolean isAdjacent(int[] square, int direction)
  { // map the direction if necessary
    return !getSquareStatus(square[0], square[1], direction);
  }


  public int[][] getDistanceHomeBoard(int player)
  { return getDistanceBoard(getPlayerFinish(player));
  }

  public int[][] getDistanceBoard(int[][] origin)
  { //# create an array the size of the board
    int[][] distanceBoard = new int[board.length][board[0].length];

    //# set all cells in array to TO_FAR value
    for(int y = distanceBoard[0].length - 1; y >= 0; y--)
    { for(int x = 0; x < distanceBoard.length; x++)
      { distanceBoard[x][y] = Integer.MAX_VALUE;
      }
    }

    //# set origin cells in array to 0
    //# add origin cells to queue
    LinkedList bfstQueue = new LinkedList();
    for(int i = 0; i < origin.length; i++)
    { distanceBoard[origin[i][0]][origin[i][1]] = 0;
      bfstQueue.addLast(origin[i]);
    }

    //# while cells in queue, add neighbours to queue, set neighbours value plus 1
    int[] current;
    int[] leaf;
    while(!bfstQueue.isEmpty())
    { current = (int[])bfstQueue.removeFirst();
      for(int d = DIR_ABOVE; d <= DIR_LEFT; d++)
      { if(isAdjacent(current, d))
        { leaf = hardquorBoardTools.getSquareInDirection(current, d);
          if(distanceBoard[leaf[0]][leaf[1]] == Integer.MAX_VALUE)
          { distanceBoard[leaf[0]][leaf[1]] = distanceBoard[current[0]][current[1]] + 1;
            bfstQueue.addLast(leaf);
    }
      }
      }
    }

    //# return board sized array
    return distanceBoard;
  }

  public Object clone()
  { return new hardquorSimpleBoard(this);
  }

  public int getWidth()
  { return board.length;
  }

  public int getHeight()
  { return board[0].length;
  }
}

